<template>
  <div id="app">
    <div class="block course1">
      <h4>{{ count }}</h4>
      <button class="mr10" @click="alert123()">alert</button>
      <button @click="add()">按钮1</button>
    </div>
    <div class="block course2">
      <input type="text" placeholder="id" v-model="state.id" />
      <span>-</span>
      <input type="text" placeholder="name" v-model="state.name" />
      <span>-</span>
      <input class="mr10" type="text" placeholder="age" v-model="state.age" />
      <button type="submit" @click.prevent="addItem()">添加</button>
      <ul>
        <li class="mb5" v-for="(v, i) in state.list" :key="i">
          <span class="mr10">{{ v.id }}--{{ v.name }}--{{ v.age }}</span>
          <button @click="remove(i)">删除</button>
        </li>
      </ul>
    </div>
    <div class="block course3">
      <h4>{{ num }}</h4>
      <button @click="addbyoption()">按钮3</button>
    </div>
    <div class="block course4">
      <h4>{{ state4 }}</h4>
      <button @click="func4()">按钮4</button>
    </div>
    <div class="block course5">
      <h4>{{ ref5.length }}</h4>
      <button @click="func5()">按钮5</button>
    </div>
    <div class="block course6">
      <p>{{ state6.a }}</p>
      <p>{{ state6.gf.b }}</p>
      <p>{{ state6.gf.f.c }}</p>
      <p>{{ state6.gf.f.s.d }}</p>
      <button @click="func6()">按钮6</button>
      <p>{{ ref6.a }}</p>
      <p>{{ ref6.gf.b }}</p>
      <p>{{ ref6.gf.f.c }}</p>
      <p>{{ ref6.gf.f.s.d }}</p>
      <button @click="funcf6()">按钮6ref</button>
    </div>
    <div class="block course7">
      <p>{{ state7.a }}</p>
      <p>{{ state7.gf.b }}</p>
      <p>{{ state7.gf.f.c }}</p>
      <p>{{ state7.gf.f.s.d }}</p>
      <button @click="func7()">按钮7</button>
      <p>{{ ref7.a }}</p>
      <p>{{ ref7.gf.b }}</p>
      <p>{{ ref7.gf.f.c }}</p>
      <p>{{ ref7.gf.f.s.d }}</p>
      <button @click="funcf7()">按钮7</button>
    </div>
    <div class="block course9">
      <p>{{ raw9 }}</p>
      <p>{{ state9 }}</p>
      <button @click="func9()">按钮9</button>
    </div>
    <div class="block course10">
      <p>{{ ref10 }}</p>
      <button @click="func10()">按钮</button>
      <p>{{ toRef10 }}</p>
      <button @click="func10t()">按钮</button>
    </div>
    <div class="block course11">
      <p>{{ toRef11.name.value }}</p>
      <p>{{ toRef11.age.value }}</p>
      <button @click="func11t()">按钮</button>
    </div>
    <div class="block course12">
      <p>{{ ref12 }}</p>
      <p>{{ result }}</p>
      <button @click="func12()">按钮</button>
    </div>
    <div class="block course13">
      <div ref="box">course13</div>
    </div>
    <div class="block course14">
      <p>{{ state14 }}</p>
      <p>{{ state14c }}</p>
      <p>{{ state14s }}</p>
      <button @click="func14()">按钮</button>
    </div>
  </div>
</template>

<script>
import {
  ref,
  reactive,
  isRef,
  isReactive,
  shallowReactive,
  shallowRef,
  triggerRef,
  toRaw,
  markRaw,
  toRef,
  toRefs,
  customRef,
  onMounted,
  readonly,
  shallowReadonly,
  isReadonly,
} from "vue";

import course2 from "./assets/js/course2";

export default {
  // 组合API的入口函数
  setup() {
    // course1
    // ref函数只能监听简单类型数据的变化，不能监听复杂类型数据的变化（对象、数组）

    let count = ref(999);

    function alert123() {
      alert(123);
    }

    const add = () => {
      count.value++;
    };

    // course2

    const { state, remove, addItem } = course2();

    // course3
    // 在 Vue2 中的代码行为称为 Option API；在 Vue3 中新增的代码行为称为 Composition API
    // 两者可以混用，Vue3 基本上做到了向下兼容，在 Vue2 中的大部分代码行为都可以直接兼容使用
    // Composition API 的本质其实就是 【注入】API
    // 即在代码执行的过程中将 setup 中暴露出来的属性或方法【注入】到 Option API 中去
    // setup 执行时机位于 beforeCreate 和 created 之间
    // beforeCreate 钩子：组件刚被创建出来，组件的 data 和 methods 还没初始化好
    // created 钩子：组件刚被创建出来，并且组件的 data 和 methods 已经初始化好
    // Composition API 中暴露出来的属性和方法是【先于】直接写在 Option API 中的属性和方法【注入】的
    // 由于 setup 的执行时机在 created 钩子之前，所以在 setup 中无法使用 data 和 methods；为了避免错误，Vue 直接将 setup 中的 this 修改为 undefined
    // setup 必须是同步的不能是异步的，本质上的目的是为了保证其执行时机正确

    // course4
    // 在 Vue2 中实现响应式数据是基于 Object.defineProperty
    // 在 Vue3 中实现响应式数据是基于 Proxy
    // 使用 reactive 本质上就是将数据包裹成一个 Proxy 对象，从而实现响应式数据
    // 因为 Proxy 只能接收【引用类型】的参数，因此 reactive 也只能包裹【引用类型】的数据

    let state4 = reactive({
      time: new Date().getTime(),
      list: [1, 2, 3],
    });

    const func4 = () => {
      console.log(state4);

      state4.time = new Date().getTime();

      state4.list.push(state4.time);

      console.log(state4);
    };

    // course5
    // 在 Vue3 中提供了 ref 简化 reactive 的使用
    // ref 的本质 ref(123) => reactive({ value:123 })
    // 在 script 中使用 ref 必须得加上 .value
    // 在 template 中使用 ref 却可以省略 .value，因为 Vue3 会帮忙自动补全
    // 就算如此，在直接使用 reactive({ value:123 }) 时，Vue3 可不会帮忙自动补全
    // 因为 Vue3 通过判断是否存在 __v_isRef 属性且为 true 可以区分出 ref 和 reactive
    // 由此衍生出 isRef 和 isReactive 方法

    let ref5 = ref([1, 2, 3]);

    const func5 = () => {
      console.log(ref5);

      console.log(isRef(ref5));
      console.log(isReactive(ref5));

      ref5.value.push(4);
    };

    // course6
    // reactive 和 ref 默认是递归监听，即可以监听任意层级的变化
    // 实现原理就是递归遍历所有层级，将每一层级都包装成 Proxy 对象

    let state6 = reactive({
      a: "a",
      gf: {
        b: "b",
        f: {
          c: "c",
          s: {
            d: "d",
          },
        },
      },
    });

    let ref6 = ref({
      a: "a",
      gf: {
        b: "b",
        f: {
          c: "c",
          s: {
            d: "d",
          },
        },
      },
    });

    const func6 = () => {
      state6.a = 1;
      state6.gf.b = 2;
      state6.gf.f.c = 3;
      state6.gf.f.s.d = 4;

      console.log(state6);
      console.log(state6.gf);
      console.log(state6.gf.f);
      console.log(state6.gf.f.s);
    };

    const funcf6 = () => {
      ref6.value.a = 1;
      ref6.value.gf.b = 2;
      ref6.value.gf.f.c = 3;
      ref6.value.gf.f.s.d = 4;

      console.log(ref6.value);
      console.log(ref6.value.gf);
      console.log(ref6.value.gf.f);
      console.log(ref6.value.gf.f.s);
    };

    // course7
    // shallowReactive 和 shallowRef 用于提供非递归监听，只监听第一层的变化
    // 应用场景：大数据量时，用于性能调优；一般情况下还是尽量使用 reactive 和 ref
    // shallowRef(123) 的本质其实就是 shallowReactive({ value:123 })

    let state7 = shallowReactive({
      a: "a",
      gf: {
        b: "b",
        f: {
          c: "c",
          s: {
            d: "d",
          },
        },
      },
    });

    const func7 = () => {
      // 只监听了第一层的变化，所以只有当第一层变化时才会更新视图
      state7.a = 1;
      state7.gf.b = 2;
      state7.gf.f.c = 3;
      state7.gf.f.s.d = 4;

      console.log(state7); // Proxy
      console.log(state7.gf);
      console.log(state7.gf.f);
      console.log(state7.gf.f.s);
    };

    let ref7 = shallowRef({
      a: "a",
      gf: {
        b: "b",
        f: {
          c: "c",
          s: {
            d: "d",
          },
        },
      },
    });

    const funcf7 = () => {
      ref7.value.a = 1;
      ref7.value.gf.b = 2;
      ref7.value.gf.f.c = 3;
      ref7.value.gf.f.s.d = 4;
      // 在 Vue3 中只提供了 triggerRef 方法用于触发视图变更，暂无 triggerReactive
      triggerRef(ref7);

      ref7.value = {
        a: "gf",
        gf: {
          b: "f",
          f: {
            c: "s",
            s: {
              d: "c",
            },
          },
        },
      };

      console.log(ref7);
      console.log(ref7.value);
      console.log(ref7.value.gf);
      console.log(ref7.value.gf.f);
      console.log(ref7.value.gf.f.s);
    };

    // course8
    // toRaw 能拿到包装数据的原始数据
    // 应用场景：只想更新数据，但是不想更新视图时可以使用
    // 因为 Proxy 包装数据变更会触发视图更新，所以需要通过 toRaw 拿到原始数据

    console.log(toRaw(state6));
    console.log(toRaw(ref6.value));
    console.log(ref6.value);
    console.log(toRaw(state7));
    console.log(toRaw(ref7.value));
    console.log(ref7.value);
    console.log(toRaw(ref7.value) === ref7.value);

    // course9
    // markRaw 标记原始数据为永远不会被追踪，即不会触发视图更新

    let raw9 = {
      name: "raw9",
      age: 18,
    };

    raw9 = markRaw(raw9);

    let state9 = reactive(raw9);

    const func9 = () => {
      state9.age = 24;
      // raw9.age = 24;
      console.log(raw9);
      console.log(state9);
    };

    // course10
    // ref 基于对象内某一个键值来创建响应式数据，修改响应式数据，并不会改变原始数据中的值，因为只是将原始数据中的值复制了一份过来使用
    // toRef 同样基于对象内某一个键值来创建响应式数据，修改响应式数据，会改变原始数据中的值，因为是将原始数据中的值的引用拿过来直接用的
    // 因此不管是修改原始数据中的值，还是修改响应式数据的值，都会彼此触发变更，但是并不会触发视图更新
    // 注意：不管是 markRaw 还是 toRef 所谓的不会触发视图更新，只是它本身行为不会触发视图更新，一旦页面中有行为会触发视图更新，依然会将其新值一起更新到页面中

    let raw10 = {
      age: 18,
    };

    let ref10 = ref(raw10.age);

    const func10 = () => {
      ref10.value = 24;
      console.log(raw10);
      console.log(ref10);
    };

    let toRef10 = toRef(raw10, "age");

    const func10t = () => {
      toRef10.value = 24;
      console.log(raw10);
      console.log(toRef10);
    };

    // course11
    // toRefs 与 toRef 功能类似，不同的地方在于后者只能基于对象内某一键值创建响应式数据，而前者可以将整个对象作为参数，遍历对象键值生成多个响应式数据然后放在集合中抛出
    let raw11 = {
      name: "mike",
      age: 18,
    };

    let toRef11 = toRefs(raw11);

    const func11t = () => {
      toRef11.name.value = "siri";
      toRef11.age.value = 24;
      console.log(toRef11);
      console.log(raw11);
    };

    // course12
    // customRef 自定义一个 ref
    const myRef = (value, delay = 500) => {
      let timer;
      return customRef((track, trigger) => {
        return {
          get() {
            // 收集依赖
            track();
            console.log("get", value);
            return value;
          },
          set(v) {
            if (timer) {
              clearTimeout(timer);
            }
            timer = setTimeout(() => {
              console.log("set", v);
              value = v;
              trigger();
            }, delay);
          },
        };
      });
    };

    let ref12 = myRef(18);

    const func12 = () => {
      console.log(ref12);
      ref12.value++;
    };

    const fetchRef = (url) => {
      let value = [];

      fetch(url)
        .then((res) => {
          return res.json();
        })
        .then((json) => {
          result.value = json;
        })
        .catch((err) => {
          console.log(err);
        });

      return customRef((track, trigger) => {
        return {
          get() {
            track();
            return value;
          },
          set(v) {
            value = v;
            trigger();
          },
        };
      });
    };

    let result = fetchRef("data.json");

    // course13
    // 在 Vue3 中可以通过 ref 获取元素，但是介于 setup 的执行时机，需要通过 onMounted 才能获取
    let box = ref(null);

    onMounted(() => {
      console.log(this);
      console.log("onMounted", box.value);
    });

    console.log("setup", box.value);

    // course14
    // readonly 递归只读 shallowReadonly 非递归只读 isReadonly 判断是否为只读
    // 不适用于原始数据，只对引用类型数据生效，如对象，数组等
    // 即便是 shallowReadonly 中的非只读属性修改成功后，也不会触发视图更新
    // isReadonly 不能判断只读数据中的属性
    let state14 = readonly({
      name: "yun",
      raw: {
        age: 18,
        sex: "male",
      },
    });

    let state14c = shallowReadonly({
      name: "yun",
      raw: {
        age: 18,
        sex: "male",
      },
    });

    const state14s = {
      name: "yun",
      raw: {
        age: 18,
        sex: "male",
      },
    };

    const func14 = () => {
      state14.name = "cy";
      state14.raw.age = 24;
      state14c.name = "cy";
      state14c.raw.age = 24;
      console.log(state14);
      console.log(state14c);
      console.log(isReadonly(state14));
      console.log(isReadonly(state14c));
      console.log(isReadonly(state14.raw.age));
      // 只读与常量的区别在于前者针对对象的属性只读，后者针对常量不可修改（重新赋值，对于引用类型数据就是修改引用地址）
      // state14s = 123;
      state14s.name = "cy";
      console.log(state14s);
    };

    return {
      count,
      alert123,
      add,
      state,
      remove,
      addItem,
      state4,
      func4,
      ref5,
      func5,
      state6,
      func6,
      ref6,
      funcf6,
      state7,
      func7,
      ref7,
      funcf7,
      raw9,
      state9,
      func9,
      ref10,
      func10,
      toRef10,
      func10t,
      toRef11,
      func11t,
      ref12,
      func12,
      result,
      box,
      state14,
      state14c,
      state14s,
      func14,
    };
  },
  data() {
    return {
      num: this.count,
    };
  },
  mounted() {
    console.log(this.$refs.box);
  },
  methods: {
    addbyoption() {
      this.num++;
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  padding: 10px;
}
.block {
  margin: 16px 0;
}
.mr10 {
  margin-right: 10px;
}
.mb5 {
  margin-bottom: 5px;
}
</style>
